package com.wjy.ioc;

import com.wjy.ioc.exception.IocException;
import com.wjy.ioc.exception.NoSuchBeanException;

import javax.annotation.Nonnull;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 基础Bean工厂实现，支持单例，多例管理
 *
 * @author wjy
 * @date 2020/9/12
 */
public class BaseBeanFactoryImpl implements BeanFactory {

    /** 单例Bean容器 */
    private final SingletonBeanGeneratorImpl singletonBeanGenerator;

    /** 多例Bean实例化工具类 */
    private final BeanGenerator baseBeanGenerator;

    /** IOC容器管理的所有BeanDefinition容器 */
    private final BeanDefinitionRegister globalBeanDefinitionRegister;

    public BaseBeanFactoryImpl(SingletonBeanGeneratorImpl singletonBeanGenerator, BeanGenerator baseBeanGenerator, BeanDefinitionRegister globalBeanDefinitionRegister) {
        this.singletonBeanGenerator = singletonBeanGenerator;
        this.baseBeanGenerator = baseBeanGenerator;
        this.globalBeanDefinitionRegister = globalBeanDefinitionRegister;
    }

    @Override
    public Object getBean(@Nonnull String name) throws NoSuchBeanException {
        return getBean(globalBeanDefinitionRegister.getBeanDefinition(name));
    }

    @Override
    public <T> T getBean(@Nonnull Class<T> beanType) throws IocException {
        return (T) getBean(globalBeanDefinitionRegister.getBeanDefinition(beanType));
    }

    @Override
    public <T> T getBean(@Nonnull String name, @Nonnull Class<T> beanType) throws NoSuchBeanException {
        final BeanDefinition definition = globalBeanDefinitionRegister.getBeanDefinition(name);
        final BeanDefinition definition1 = globalBeanDefinitionRegister.getBeanDefinition(beanType);
        if (!definition.equals(definition1)) {
            throw new IocException("name获取BeanDefinition的类型和banType不匹配 name:" + name + ",value:" + beanType.getName());
        }
        return getBean(beanType);
    }

    /**
     * 根据类型获取所有当前类型在IOC容器中的Bean(不区分或者 单例/多例)
     *
     * @param beanType Bean类型-可以是接口，父类的类型
     * @param <T>      参数类型
     * @return key为IOC容器beanName，value为管理Bean
     */
    public <T> Map<String, T> getBeans(@Nonnull Class<T> beanType) {
        final HashMap<String, T> map = new HashMap<>();
        final List<BeanDefinition> beanDefinitions = globalBeanDefinitionRegister.getBeanDefinitions(beanType);
        for (BeanDefinition beanDefinition : beanDefinitions) {
            map.put(beanDefinition.getBeanName(), (T) getBean(beanDefinition));
        }
        return map;
    }

    private Object getBean(@Nonnull BeanDefinition beanDefinition) throws NoSuchBeanException {
        if (beanDefinition.isSingleton()) {
            return singletonBeanGenerator.getSingletonObjects().getBean(beanDefinition.getBeanName());
        } else {
            return baseBeanGenerator.createBean(beanDefinition);
        }
    }

    @Override
    public Class<?> getType(@Nonnull String name) throws NoSuchBeanException {
        return globalBeanDefinitionRegister.getBeanDefinition(name).getBeanClass();
    }

    @Override
    public Object removeBean(@Nonnull String name) {
        final BeanDefinition beanDefinition = globalBeanDefinitionRegister.removeBeanDefinition(name);
        if (beanDefinition.isSingleton()) {
            return singletonBeanGenerator.getSingletonObjects().removeBean(name);
        } else {
            return null;
        }
    }

    @Override
    public boolean containsBean(@Nonnull String name) {
        return globalBeanDefinitionRegister.isBeanNameInUse(name);
    }

    @Override
    public <T> boolean containsBean(@Nonnull Class<T> beanType) {
        return globalBeanDefinitionRegister.containsDefinition(beanType);
    }

    @Override
    public boolean isTypeMatch(@Nonnull String name, @Nonnull Class<?> typeToMatch) {
        final BeanDefinition definition = globalBeanDefinitionRegister.getBeanDefinition(name);
        return definition.getBeanClass().equals(typeToMatch);
    }

    public BeanFactory getSingletonBeanFactory() {
        return singletonBeanGenerator.getSingletonObjects();
    }

    public BeanGenerator getBaseBeanGenerator() {
        return baseBeanGenerator;
    }

    public BeanDefinitionRegister getGlobalBeanDefinitionRegister() {
        return globalBeanDefinitionRegister;
    }
}
